/*
 * Middleware
 * Copyright (C) 2010/11
 * Riccardo Pulvirenti, Giuseppe Ravidà, Andrea Tino, Davide Monaco
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANDABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see http://www.gnu.org/licenses
 */

/*!
 * SynQueue v1.1
 * File: synqueue.cpp
 */

#include <iostream>
#include <deque>
#include <boost/thread/mutex.hpp>
#include <boost/thread/condition.hpp>

#ifndef _SYNQUEUE_CPP_
#define _SYNQUEUE_CPP_

//-----------------------------------------------------------------------------
// Classes definition
namespace middleware {
namespace queueing {
/*! \class SynQueue
 * \brief Synchronized queue for generic elements.
 *
 * Communication queue providing a circular queue for generic data.
 * T defines the type of elements stored inside the std::deque.
 * SIZE defines the size of the queue. If SIZE is 0, then an infinite queue
 * is considered.
 * \tparam T Type of the elements in the SynQueue
 * \param SIZE Defined the max elements in the SynQueue, if this is 0, then
 * the SynQueue is not limited.
 */
template < typename T, unsigned long SIZE = 0 >
class SynQueue {
public:
	typedef std::allocator< T > Allocator;
	typedef unsigned int SizeType;
	typedef typename Allocator::reference Reference;
	typedef typename Allocator::const_reference ConstReference;
	/*!
	 * It is possible to create a CommQueue specifying no parameters, the
	 * queue will automatically be created with zero elements.
	 */
	SynQueue();
	/*!
	 * Copy constructor.
	 */
	SynQueue(const SynQueue& queue);
	/*!
	 * Destructor.
	 */
	~SynQueue();
	/*!
	 * Inserting in queue, element is copied in queue. New element is
	 * inserted on bottom. This call can be a blocking call if the SynQueue
	 * reached its limit, in this case caller blocks until another thread
	 * dequeues something.
	 * \param el Element to insert.
	 */
	void enqueue(const T& el);
	/*!
	 * Extracting from queue, a reference to the element on top is
	 * returned while the element is deleted from the queue. A copy
	 * of the extracted element is returned. This call can be a blocking
	 * call if the SynQueue reached 0 elements, in this case caller blocks
	 * until another thread enqueues something.
	 * \return Returns a copy of the dequeued element.
	 */
	T dequeue();
	/*!
	 * Tells whether the queue is empty. This call is blocking in the case
	 * another thread is working on the SynQueue.
	 * \return Returns true if the SynQueue is empty, false otherwise.
	 */
	bool isEmpty() {
		bool is_empty = false;
		// Getting scoped lock from my mutex, this automatically locks mutex
		boost::mutex::scoped_lock lck(this->_queue_monitor);
		// Checking
		if ((this->_data).empty()) is_empty = true;
		lck.unlock();
		return is_empty;
	}
	/*!
	 * Tells whether the queue is full. This call is blocking in the case
	 * another thread is working on the SynQueue.
	 * \return Returns true if the SynQueue is full, false otherwise.
	 */
	bool isFull() {
		bool is_full = false;
		boost::mutex::scoped_lock lck(this->_queue_monitor); /* Locks mutex */
		// Checking
		if ((this->_data).size() == SIZE) is_full = true;
		lck.unlock();
		return is_full;
	}
	/*!
	 * Tells the queue's present size. This call is blocking in the case
	 * another thread is working on the SynQueue.
	 * \return Returns true if the current size of the SynQueue.
	 */
	SizeType size() {
		SizeType queue_size = 0;
		boost::mutex::scoped_lock lck(this->_queue_monitor); /* Locks mutex */
		// Checking
		queue_size = (SizeType)((this->_data).size());
		lck.unlock();
		return queue_size;
	}
protected:
	/*!
	 * Stores the elements of the queue.
	 */
	std::deque< T > _data;
	/*!
	 * Mutex for mutual exclusion management.
	 */
	boost::mutex _queue_monitor;
	/*!
	 * Condition variable for waiting/awakening for/from empty queue.
	 */
	boost::condition _queue_empty;
	/*!
	 * Condition variable for waiting/awakening for/from full queue.
	 */
	boost::condition _queue_full;
};
} /* queueing */
} /* middleware */
//-----------------------------------------------------------------------------

/*!
 * CommQueue: Implementations.
 */

using namespace middleware::queueing;

//-----------------------------------------------------------------------------
// Public members
/* Default constructor */
template < typename T, unsigned long SIZE >
SynQueue<T, SIZE>::SynQueue() {
	// Creating dequeue --> automatically created as instance member
}
/* Copy constructor */
template < typename T, unsigned long SIZE >
SynQueue<T, SIZE>::SynQueue(const SynQueue& queue) {

}
/* Destructor */
template < typename T, unsigned long SIZE >
SynQueue<T, SIZE>::~SynQueue() {
	// Mutexes and condition variables are automatically destroyed
}
/* Enqueueing */
template < typename T, unsigned long SIZE >
void SynQueue<T, SIZE>::enqueue(const T& el) {
	// Getting scoped lock from my mutex, this automatically locks mutex
	boost::mutex::scoped_lock lck(this->_queue_monitor);
	/* Checking queue is not full */
	while (SIZE > 0 && (this->_data).size() == SIZE)
		(this->_queue_full).wait(lck);
	(this->_data).push_back(el);
	/* Awake those waiting for queue to not be emtpy */
	(this->_queue_empty).notify_all();
	// Unlock mutes and get out of critical region
	lck.unlock();
}
/* Dequeueing */
template < typename T, unsigned long SIZE >
T SynQueue<T, SIZE>::dequeue() {
	// Getting scoped lock from my mutex, this automatically locks mutex
	boost::mutex::scoped_lock lck(this->_queue_monitor);
	/* Checking queue is not empty */
	while ((this->_data).empty()) (this->_queue_empty).wait(lck);
	T element = (this->_data).front();
	(this->_data).pop_front();
	/* Awake those waiting for queue to not be full */
	(this->_queue_full).notify_all();
	// Unlock mutes and get out of critical region
	lck.unlock();
	return element;
}

#endif
